/*
 * Copyright 2022 Rich yang, 18158898020@189.com
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied,
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */

`include "defines.v"

module ex(
	input wire	rst,

	input wire[`RegBus]	inst_i,
	// inst decode to execute stage signal
	input wire[`AluOpBus]	aluop_i,
	input wire[`AluSelBus]	alusel_i,
	input wire[`RegBus]	reg1_i,
	input wire[`RegBus]	reg2_i,
	input wire[`RegBus]	branch_target_op1_i,
	input wire[`RegBus]	branch_target_op2_i,
	input wire[`RegAddrBus]	wd_i,
	input wire		wreg_i,

	input wire[`DoubleRegBus] div_result_i,
	input wire		div_ready_i,

	// branch
	input wire[`RegBus]	link_address_i,

	// execute result
	output reg[`RegAddrBus]	wd_o,
	output reg		wreg_o,
	output reg[`RegBus]	wdata_o,

	// div op
	output reg[`RegBus]	div_opdata1_o,
	output reg[`RegBus]	div_opdata2_o,
	output reg		div_start_o,
	output reg		signed_div_o,

	output wire[`AluOpBus]	aluop_o,
	output wire[`RegBus]	mem_addr_o,
	output wire[`RegBus]	reg2_o,

	output wire		stallreq,
	output wire		branch_flag_o,
	output wire[`RegBus] branch_target_address_o
);


// save logical calculate result
	reg[`RegBus] logicout;
// save shift calculate result
	reg[`RegBus] shiftres;
// save arithmetic result
	reg[`RegBus] arithmeticres;
	wire[`RegBus] reg2_i_mux;
	reg[`RegBus] reg1_i_mux;
	wire[`RegBus] result_sum;
	wire ov_sum;
	wire reg1_eq_reg2;
	wire reg1_lt_reg2;
// save mul result
	wire[`RegBus] opdata1_mult;
	wire[`RegBus] opdata2_mult;
	wire[`DoubleRegBus] hilo_temp;
	reg[`DoubleRegBus] mulres_temp;
	wire[`RegBus] mulres;

// div
	reg stallreq_for_div;
	wire[`RegBus] divres;


// branch
	reg branch_flag;
	reg[`RegBus] branch_target_address;

// load store
	assign aluop_o = aluop_i;

	assign branch_flag_o = branch_flag;
	assign branch_target_address_o = branch_target_address;

	assign stallreq = stallreq_for_div;

// stage1: according `aluop_i` to logic calculate
	always @ (*) begin
		if (rst == `RstEnable) begin
			logicout <= `ZeroWord;
		end else begin
			case (aluop_i)
			`EXE_AND_OP: begin
				logicout <= reg1_i & reg2_i;
			end
			`EXE_OR_OP: begin
				logicout <= reg1_i | reg2_i;
			end
			`EXE_XOR_OP: begin
				logicout <= reg1_i ^ reg2_i;
			end
			default: begin
				logicout <= `ZeroWord;
			end
			endcase
		end
	end


// stage2: according `aluop_i` to shift
	always @ (*) begin
		if (rst == `RstEnable) begin
			shiftres <= `ZeroWord;
		end else begin
			case (aluop_i)
			`EXE_SLL_OP: begin
				shiftres <= reg1_i << reg2_i[4:0];
			end
			`EXE_SRL_OP: begin
				shiftres <= reg1_i >> reg2_i[4:0];
			end
			`EXE_SRA_OP: begin
				shiftres <= ({32{reg1_i[31]}} << (6'd32-{1'b0, reg2_i[4:0]})) | reg1_i >> reg2_i[4:0];
			end
			default: begin
				shiftres <= `ZeroWord;
			end
			endcase
		end
	end


// stage3: according `aluop_i` to arithmetic
	assign reg2_i_mux = ((aluop_i == `EXE_SUB_OP) ||
			     (aluop_i == `EXE_SLT_OP) ||
			     (aluop_i == `EXE_BLT_OP) ||
			     (aluop_i == `EXE_BGE_OP)) ? (~reg2_i)+1 : reg2_i;
	assign result_sum = reg1_i + reg2_i_mux;

	assign ov_sum = ((!reg1_i[31] && !reg2_i_mux[31]) && result_sum[31]) || ((reg1_i[31] && reg2_i_mux[31]) && (!result_sum[31]));

	assign reg1_lt_reg2 = ((aluop_i == `EXE_SLT_OP || aluop_i == `EXE_BLT_OP || aluop_i == `EXE_BGE_OP)) ?
				((reg1_i[31] && !reg2_i[31]) ||
				(!reg1_i[31] && !reg2_i[31] && result_sum[31]) ||
				(reg1_i[31] && reg2_i[31] && result_sum[31]))
				: ((aluop_i == `EXE_SLTU_OP || aluop_i == `EXE_BLTU_OP || aluop_i == `EXE_BGEU_OP) ? (reg1_i < reg2_i) : 1'b0);




	assign reg1_eq_reg2 = ((aluop_i == `EXE_BEQ_OP || aluop_i == `EXE_BNE_OP)) ? ((reg1_i - reg2_i) == 32'h0) : 1'b0;

	always @ (*) begin
		if (rst == `RstEnable) begin
			arithmeticres <= `ZeroWord;
		end else begin
			case (aluop_i)
				`EXE_ADD_OP: begin
					arithmeticres <= result_sum;
				end
				`EXE_SUB_OP: begin
					arithmeticres <= result_sum;
				end
				`EXE_SLT_OP, `EXE_SLTU_OP: begin
					arithmeticres <= reg1_lt_reg2;
				end
				`EXE_AUIPC_OP: begin
					arithmeticres <= branch_target_op1_i + branch_target_op2_i;
				end
				default: begin
					arithmeticres <= `ZeroWord;
				end
			endcase
		end
	end



	assign opdata1_mult = (((aluop_i == `EXE_MUL_OP || aluop_i == `EXE_MULH_OP || aluop_i == `EXE_MULHSU_OP)) && (reg1_i[31] == 1'b1)) ? ((~reg1_i) + 1) : reg1_i;
	assign opdata2_mult = (((aluop_i == `EXE_MUL_OP || aluop_i == `EXE_MULH_OP)) && (reg2_i[31] == 1'b1)) ? ((~reg2_i) + 1) : reg2_i;

	assign hilo_temp = opdata1_mult * opdata2_mult;

	always @ (*) begin
		if (rst == `RstEnable) begin
			mulres_temp <= {`ZeroWord, `ZeroWord};
		end else if ((aluop_i == `EXE_MUL_OP)) begin
			mulres_temp <= hilo_temp;
		end else if ((aluop_i == `EXE_MULH_OP)) begin
			if (reg1_i[31] ^ reg2_i[31] == 1'b1) begin
				mulres_temp <= (~hilo_temp) + 1;
			end else begin
				mulres_temp <= hilo_temp;
			end
		end else if (aluop_i == `EXE_MULHU_OP) begin
			mulres_temp = hilo_temp;
		end else if (aluop_i == `EXE_MULHSU_OP) begin
			if (reg1_i[31] == 1'b1) begin
				mulres_temp <= (~hilo_temp) + 1;
			end else begin
				mulres_temp <= hilo_temp;
			end
		end else begin
			mulres_temp <= hilo_temp;
		end
	end

	assign mulres = ((aluop_i == `EXE_MUL_OP) ? mulres_temp[31:0] : ((aluop_i == `EXE_MULH_OP || aluop_i == `EXE_MULHU_OP || aluop_i == `EXE_MULHSU_OP)? mulres_temp[63:32] : mulres_temp[31:0]));

	always @ (*) begin
		if (rst == `RstEnable) begin
			stallreq_for_div	<= `NoStop;
			div_opdata1_o		<= `ZeroWord;
			div_opdata2_o		<= `ZeroWord;
			div_start_o		<= `DivStop;
			signed_div_o		<= 1'b0;
		end else begin
			stallreq_for_div	<= `NoStop;
			div_opdata1_o		<= `ZeroWord;
			div_opdata2_o		<= `ZeroWord;
			div_start_o		<= `DivStop;
			signed_div_o		<= 1'b0;
			case (aluop_i)
				`EXE_DIV_OP, `EXE_REM_OP: begin
					if (div_ready_i == `DivResultNotReady) begin
						div_opdata1_o <= reg1_i;
						div_opdata2_o <= reg2_i;
						div_start_o   <= `DivStart;
						signed_div_o  <= 1'b1;
						stallreq_for_div <= `Stop;
					end else if (div_ready_i == `DivResultReady) begin
						div_opdata1_o <= reg1_i;
						div_opdata2_o <= reg2_i;
						div_start_o   <= `DivStop;
						signed_div_o  <= 1'b1;
						stallreq_for_div <= `NoStop;
					end else begin
						div_opdata1_o <= `ZeroWord;
						div_opdata2_o <= `ZeroWord;
						div_start_o   <= `DivStop;
						signed_div_o  <= 1'b0;
						stallreq_for_div <= `NoStop;
					end
				end
				`EXE_DIVU_OP, `EXE_REMU_OP: begin
					if (div_ready_i == `DivResultNotReady) begin
						div_opdata1_o <= reg1_i;
						div_opdata2_o <= reg2_i;
						div_start_o   <= `DivStart;
						signed_div_o  <= 1'b0;
						stallreq_for_div <= `Stop;
					end else if (div_ready_i == `DivResultReady) begin
						div_opdata1_o <= reg1_i;
						div_opdata2_o <= reg2_i;
						div_start_o   <= `DivStop;
						signed_div_o  <= 1'b0;
						stallreq_for_div <= `NoStop;
					end else begin
						div_opdata1_o <= `ZeroWord;
						div_opdata2_o <= `ZeroWord;
						div_start_o   <= `DivStop;
						signed_div_o  <= 1'b0;
						stallreq_for_div <= `NoStop;
					end
				end
				default: begin
				end
			endcase
		end
	end


	assign divres = ((div_ready_i == `DivResultReady)?((aluop_i == `EXE_DIV_OP || aluop_i == `EXE_DIVU_OP)?div_result_i[31:0]:((aluop_i == `EXE_REM_OP || aluop_i == `EXE_REMU_OP)?div_result_i[63:32]:`ZeroWord)):divres);


	assign mem_addr_o = reg1_i + (aluop_i == `EXE_SB_OP || aluop_i == `EXE_SH_OP || aluop_i == `EXE_SW_OP)? {{20{inst_i[31]}}, inst_i[31:25],inst_i[11:7]}: ((aluop_i == `EXE_LB_OP || aluop_i == `EXE_LBU_OP || aluop_i == `EXE_LH_OP || aluop_i == `EXE_LHU_OP || aluop_i == `EXE_LW_OP)?{{20{inst_i[31]}}, inst_i[31:20]} : `ZeroWord);

	assign reg2_o	= reg2_i;

	always @ (*) begin
		if (rst == `RstEnable) begin
			branch_flag		<= `NotBranch;
			branch_target_address <= `ZeroWord;
		end else if (aluop_i == `EXE_JAL_OP) begin
			branch_flag		<= `Branch;
			branch_target_address <= branch_target_op1_i + branch_target_op2_i;
		end else if (aluop_i == `EXE_JALR_OP) begin
			branch_flag		<= `Branch;
			branch_target_address <= (branch_target_op1_i + branch_target_op2_i) & {32'hFFFE};
		end else if (aluop_i == `EXE_BEQ_OP) begin
			if (reg1_eq_reg2 == 1'b1) begin
				branch_flag <= `Branch;
				branch_target_address <= branch_target_op1_i + branch_target_op2_i;
			end else begin
				branch_flag <= `NotBranch;
				branch_target_address <= `ZeroWord;
			end
		end else if (aluop_i == `EXE_BNE_OP) begin
			if (reg1_eq_reg2 == 1'b0) begin
				branch_flag <= `Branch;
				branch_target_address <= branch_target_op1_i + branch_target_op2_i;
			end else begin
				branch_flag <= `NotBranch;
				branch_target_address <= `ZeroWord;
			end
		end else if (aluop_i == `EXE_BLT_OP) begin
			if (reg1_lt_reg2 == 1'b1) begin
				branch_flag <= `Branch;
				branch_target_address <= branch_target_op1_i + branch_target_op2_i;
			end else begin
				branch_flag <= `NotBranch;
				branch_target_address <= `ZeroWord;
			end
		end else if (aluop_i == `EXE_BGE_OP) begin
			if (reg1_lt_reg2 == 1'b0) begin
				branch_flag <= `Branch;
				branch_target_address <= branch_target_op1_i + branch_target_op2_i;
			end else begin
				branch_flag <= `NotBranch;
				branch_target_address <= `ZeroWord;
			end
		end else if (aluop_i == `EXE_BLTU_OP) begin
			if (reg1_lt_reg2 == 1'b1) begin
				branch_flag <= `Branch;
				branch_target_address <= branch_target_op1_i + branch_target_op2_i;
			end else begin
				branch_flag <= `NotBranch;
				branch_target_address <= `ZeroWord;
			end
		end else if (aluop_i == `EXE_BGEU_OP) begin
			if (reg1_lt_reg2 == 1'b0) begin
				branch_flag <= `Branch;
				branch_target_address <= branch_target_op1_i + branch_target_op2_i;
			end else begin
				branch_flag <= `NotBranch;
				branch_target_address <= `ZeroWord;
			end

		end else begin
			branch_flag		<= `NotBranch;
			branch_target_address <= `ZeroWord;
		end
	end

// according `alusel_i` to data out
	always @ (*) begin
		wd_o	<= wd_i;
		wreg_o	<= wreg_i;
		case (alusel_i)
			`EXE_RES_LOGIC: begin
				wdata_o <= logicout;
			end
			`EXE_RES_SHIFT: begin
				wdata_o <= shiftres;
			end
			`EXE_RES_ARITHMETIC: begin
				wdata_o <= arithmeticres;
			end
			`EXE_RES_MUL: begin
				wdata_o <= mulres;
			end
			`EXE_RES_DIV: begin
				wdata_o <= divres;
			end
			`EXE_RES_JUMP_BRANCH: begin
				wdata_o <= link_address_i;
			end
		default: begin
			wdata_o <= `ZeroWord;
		end
		endcase
	end

endmodule
